﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace Chisel.Core
{
	/// <summary>
	/// A wrapper around an actor that allows the user to resize it.
	/// </summary>
	public class ResizeWrapper : Wrapper
	{
		/// <summary>
		/// Possible locations of the anchors relative to the actor.
		/// </summary>
		public enum AnchorLocation { N, NE, E, SE, S, SW, W, NW }

		/// <summary>
		/// Create the resize wrapper and adds it to the actor's canvas.
		/// </summary>
		public ResizeWrapper(Actor actor) : base(actor)
		{
			// create the anchors
			foreach (var loc in EnumHelper.GetValues<AnchorLocation>())
			{
				_anchors[loc] = new ResizeAnchor(this);
				LocalCanvas.Children.Add(_anchors[loc]);

				if (loc == AnchorLocation.N || loc == AnchorLocation.S)
					_anchors[loc].Cursor = Cursors.SizeNS;
				else if (loc == AnchorLocation.NE || loc == AnchorLocation.SW)
					_anchors[loc].Cursor = Cursors.Hand;
				else if (loc == AnchorLocation.E || loc == AnchorLocation.W)
					_anchors[loc].Cursor = Cursors.SizeWE;
				else if (loc == AnchorLocation.SE || loc == AnchorLocation.NW)
					_anchors[loc].Cursor = Cursors.Hand;
			}

			Visibility = Visibility.Collapsed;
		}


		/// <summary>
		/// The anchors.
		/// </summary>
		private Dictionary<AnchorLocation, ResizeAnchor> _anchors = new Dictionary<AnchorLocation, ResizeAnchor>();


		#region Layout

		protected override void ComputeLayout()
		{
			Point actorPos = Actor.Position;
			double w = Actor.ActualWidth;
			double h = Actor.ActualHeight;
			double t = ResizeAnchor.Thickness;

			MoveTo(actorPos.X - t/2, actorPos.Y - t/2);
			Width = w + t;
			Height = h + t;

			_anchors[AnchorLocation.N].MoveTo(w/2, 0);
			_anchors[AnchorLocation.NE].MoveTo(w, 0);
			_anchors[AnchorLocation.E].MoveTo(w, h/2);
			_anchors[AnchorLocation.SE].MoveTo(w, h);
			_anchors[AnchorLocation.S].MoveTo(w/2, h);
			_anchors[AnchorLocation.SW].MoveTo(0, h);
			_anchors[AnchorLocation.W].MoveTo(0, h/2);
			_anchors[AnchorLocation.NW].MoveTo(0, 0);

		}

		#endregion


		#region Selection

		protected override void OnSelectionChanged(Actor actor)
		{
			base.OnSelectionChanged(actor);

			if (Actor.State.IsSelected)
			{
				ComputeLayout();
				Visibility = Visibility.Visible;
			}
			else
				Visibility = Visibility.Collapsed;
		}

		#endregion


		#region Anchor Movement

		/// <summary>
		/// The point opposite to the current dragging.
		/// </summary>
		private Point _dragOpp;

		/// <summary>
		/// Handles the beginning of an anchor drag event.
		/// </summary>
		internal void OnAnchorBeginDrag(ResizeAnchor anchor)
		{
			var pos = Actor.Position;
			var left = pos.X;
			var top = pos.Y;
			var w = Actor.ActualWidth;
			var h = Actor.ActualHeight;
			var right = left + w;
			var bottom = top + h;

			var loc = from k in _anchors.Keys
					  where _anchors[k] == anchor
					  select k;
			switch ((AnchorLocation)loc.First())
			{
				case AnchorLocation.N:
					_dragOpp = new Point(left + w/2, bottom);
					break;

				case AnchorLocation.NE:
					_dragOpp = new Point(left, bottom);
					break;

				case AnchorLocation.E:
					_dragOpp = new Point(left, top + h/2);
					break;

				case AnchorLocation.SE:
					_dragOpp = new Point(left, top);
					break;

				case AnchorLocation.S:
					_dragOpp = new Point(left + w / 2, top);
					break;

				case AnchorLocation.SW:
					_dragOpp = new Point(right, top);
					break;

				case AnchorLocation.W:
					_dragOpp = new Point(right, top + h/2);
					break;

				case AnchorLocation.NW:
					_dragOpp = new Point(right, bottom);
					break;
			}
		}

		/// <summary>
		/// Handles an anchor being moved to the given local and absolute position.
		/// </summary>
		internal void OnAnchorDragged(ResizeAnchor anchor, Point relPos, Point absPos)
		{
			Point actorPos = Actor.Position;
			double w = Actor.ActualWidth;
			double h = Actor.ActualHeight;
			double newW = w;
			double newH = h;
			Point newPos = actorPos;

			var loc = from k in _anchors.Keys
					  where _anchors[k] == anchor
					  select k;
			switch ((AnchorLocation)loc.First())
			{
				case AnchorLocation.N:
					newPos.Y = absPos.Y;
					newPos = Actor.ParentCanvas.ValidatePoint(newPos);
					Actor.Position = newPos;
					Actor.Height = _dragOpp.Y - newPos.Y;
					break;

				case AnchorLocation.NE:
					newPos.Y = absPos.Y;
					newPos = Actor.ParentCanvas.ValidatePoint(newPos);
					Actor.Position = newPos;
					Actor.Height = _dragOpp.Y - newPos.Y;
					newH = _dragOpp.Y - newPos.Y;
					Actor.Width = relPos.X;
					Actor.ParentCanvas.ValidateActorSize(Actor);
					break;

				case AnchorLocation.E:
					Actor.Width = relPos.X;
					Actor.ParentCanvas.ValidateActorSize(Actor);
					break;

				case AnchorLocation.SE:
					Actor.Width = relPos.X;
					Actor.Height = relPos.Y;
					Actor.ParentCanvas.ValidateActorSize(Actor);
					break;

				case AnchorLocation.S:
					Actor.Height = relPos.Y;
					Actor.ParentCanvas.ValidateActorSize(Actor);
					break;

				case AnchorLocation.SW:
					newPos.X = absPos.X;
					newPos = Actor.ParentCanvas.ValidatePoint(newPos);
					Actor.Position = newPos;
					Actor.Width = _dragOpp.X - newPos.X;
					Actor.Height = relPos.Y;
					Actor.ParentCanvas.ValidateActorSize(Actor);
					break;

				case AnchorLocation.W:
					newPos.X = absPos.X;
					newPos = Actor.ParentCanvas.ValidatePoint(newPos);
					Actor.Position = newPos;
					Actor.Width = _dragOpp.X - newPos.X;
					break;

				case AnchorLocation.NW:
					newPos.Y = absPos.Y;
					newPos.X = absPos.X;
					newPos = Actor.ParentCanvas.ValidatePoint(newPos);
					Actor.Position = newPos;
					Actor.Height = _dragOpp.Y - newPos.Y;
					Actor.Width = _dragOpp.X - newPos.X;
					break;
			}

			// ensure the size is okay
			//double minH, minW;
			//Actor.GetMinSize(out minW, out minH);
			//Actor.Width = Math.Max(newW, minW);
			//Actor.Height = Math.Max(newH, minH);
			//Actor.Position = newPos;

			//Actor.ParentCanvas.ValidateActorPosition(Actor);
		}

		#endregion

	}


	/// <summary>
	/// Anchor for resizing.
	/// </summary>
	internal class ResizeAnchor : UserControl
	{

		internal ResizeAnchor(ResizeWrapper wrapper)
			: base()
		{
			_wrapper = wrapper;

			_square = new Rectangle() {
				Width = Thickness,
				Height = Thickness,
				Fill = FillBrush
			};
			Content = _square;
		}

		private ResizeWrapper _wrapper;

		private Rectangle _square;


		private static double _thickness = 8;
		/// <summary>
		/// Thickness of the anchors.
		/// </summary>
		internal static double Thickness
		{
			get { return _thickness; }
		}

		private static Brush _fillBrush = new SolidColorBrush(Colors.Blue) {
			Opacity = 0.5
		};
		/// <summary>
		/// Fill for the anchors.
		/// </summary>
		internal static Brush FillBrush
		{
			get { return _fillBrush; }
		}
		
		/// <summary>
		/// Moves the controls to the given location.
		/// </summary>
		internal void MoveTo(double x, double y)
		{
			Canvas.SetLeft(this, x);
			Canvas.SetTop(this, y);
		}


		#region Mouse Interaction

		private Point _offset;

		private bool _isDragging = false;

		protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
		{
			base.OnMouseLeftButtonDown(e);
			_isDragging = true;
			CaptureMouse();

			// compute the offset
			var myPos = new Point(Canvas.GetLeft(this), Canvas.GetTop(this));
			var hitPos = e.GetPosition(Parent as Canvas);
			_offset = new Point(hitPos.X - myPos.X - _thickness, hitPos.Y - myPos.Y - _thickness);
			_wrapper.OnAnchorBeginDrag(this);
			e.Handled = true;
		}
		
		protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
		{
			base.OnMouseLeftButtonUp(e);
			ReleaseMouseCapture();
			_isDragging = false;

			e.Handled = true;
		}

		protected override void OnMouseMove(MouseEventArgs e)
		{
			base.OnMouseMove(e);

			if (_isDragging)
			{
				// alert the wrapper
				var hitPos = e.GetPosition(Parent as Canvas);
				var dragPos = new Point(hitPos.X + _offset.X, hitPos.Y + _offset.Y);
				var absPos = e.GetPosition(_wrapper.Actor.ParentCanvas);
				_wrapper.OnAnchorDragged(this, dragPos, absPos);
			}
		}

		#endregion

	}

}
